package indi.yangshenhui.tool;

import indi.yangshenhui.tool.bean.LinuxShellBean;
import indi.yangshenhui.tool.exception.LinuxShellException;

import java.io.IOException;
import java.io.InputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SCPClient;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;

public class LinuxShellUtil {
	private static final Logger LOGGER = LoggerFactory.getLogger(LinuxShellUtil.class);
	private Connection connection;
	private String ip;
	private int port;
	private String username;
	private String password;
	private static final String CHARSET = "UTF-8";
	private static final int TIME_OUT = 1000 * 60 * 3;

	public LinuxShellUtil() {
	}

	public LinuxShellUtil(String ip, String username, String password) {
		this.ip = ip;
		this.username = username;
		this.password = password;
	}

	public LinuxShellUtil(String ip, int port, String username, String password) {
		this.ip = ip;
		this.port = port;
		this.username = username;
		this.password = password;
	}

	private boolean connection() throws IOException {
		LOGGER.info(String.format("connection init ip [%s] port [%s] username [%s]", ip, port, username));
		if (port == 0) {
			connection = new Connection(ip);
		} else {
			connection = new Connection(ip, port);
		}
		connection.connect();
		boolean flag = connection.authenticateWithPassword(username, password);
		if (flag) {
			LOGGER.info("authentication success");
		} else {
			LOGGER.error("authentication failure");
		}
		return flag;
	}

	public LinuxShellBean execute(String command) throws LinuxShellException {
		if (StringUtil.isEmpty(command)) {
			return null;
		}
		LOGGER.info("run shell command [" + command + "]");
		InputStream stdOut = null;
		InputStream stdErr = null;
		LinuxShellBean linuxShellBean = new LinuxShellBean();
		int mark = -1;
		try {
			if (connection()) {
				Session session = connection.openSession();
				session.execCommand(command);
				stdOut = new StreamGobbler(session.getStdout());
				linuxShellBean.setOutStr(processStream(stdOut));
				stdErr = new StreamGobbler(session.getStderr());
				linuxShellBean.setOutErr(processStream(stdErr));
				/**
				 * 一般情况下shell脚本正常执行完毕,getExitStatus方法返回0, 此方法通过远程命令取得Exit
				 * Code/status,但并不是每个server设计时都会返回这个值,如果没有则会返回null,
				 * 在调用getExitStatus时,要先调用WaitForCondition方法,通过ChannelCondition.
				 * java接口的定义可以看到每个条件的具体含义
				 */
				session.waitForCondition(ChannelCondition.EXIT_STATUS, TIME_OUT);
				mark = session.getExitStatus().intValue();
				session.close();
			}
			linuxShellBean.setMark(mark);
		} catch (IOException e) {
			throw new LinuxShellException(e);
		} finally {
			close(connection, stdOut, stdErr);
		}
		return linuxShellBean;
	}

	public LinuxShellBean executeLocal(String command) throws LinuxShellException {
		if (StringUtil.isEmpty(command)) {
			return null;
		}
		LOGGER.info("run local shell command [" + command + "]");
		Runtime runtime = Runtime.getRuntime();
		Process process = null;
		InputStream stdOut = null;
		InputStream stdErr = null;
		LinuxShellBean linuxShellBean = new LinuxShellBean();
		int mark = -1;
		try {
			process = runtime.exec(command);
			stdOut = new StreamGobbler(process.getInputStream());
			linuxShellBean.setOutStr(processStream(stdOut));
			stdErr = new StreamGobbler(process.getErrorStream());
			linuxShellBean.setOutErr(processStream(stdErr));
			mark = process.exitValue();
			linuxShellBean.setMark(mark);
		} catch (IOException e) {
			throw new LinuxShellException(e);
		} finally {
			close(null, stdOut, stdErr);
		}
		return linuxShellBean;
	}

	public boolean executeScpUpload(String[] localFiles, String remoteDirectory) throws LinuxShellException {
		boolean flag = false;
		if (localFiles == null || localFiles.length == 0 || StringUtil.isEmpty(remoteDirectory)) {
			return flag;
		}
		LOGGER.info("scp upload " + array2String(localFiles) + " to [" + ip + " " + remoteDirectory + "]");
		try {
			if (connection()) {
				SCPClient scpClient = new SCPClient(connection);
				scpClient.put(localFiles, remoteDirectory);
				flag = true;
			}
		} catch (IOException e) {
			throw new LinuxShellException(e);
		} finally {
			close(connection, null, null);
		}
		return flag;
	}

	public boolean executeScpUpload(String localFile, String remoteDirectory) throws LinuxShellException {
		return executeScpUpload(new String[] { localFile }, remoteDirectory);
	}

	public boolean executeScpDownload(String[] remoteFiles, String localDirectory) throws LinuxShellException {
		boolean flag = false;
		if (remoteFiles == null || remoteFiles.length == 0 || StringUtil.isEmpty(localDirectory)) {
			return flag;
		}
		LOGGER.info("scp download " + array2String(remoteFiles) + " from [" + ip + "] to [" + localDirectory + "]");
		try {
			if (connection()) {
				SCPClient scpClient = new SCPClient(connection);
				scpClient.get(remoteFiles, localDirectory);
				flag = true;
			}
		} catch (IOException e) {
			throw new LinuxShellException(e);
		} finally {
			close(connection, null, null);
		}
		return flag;
	}

	public boolean executeScpDownload(String remoteFile, String localDirectory) throws LinuxShellException {
		return executeScpDownload(new String[] { remoteFile }, localDirectory);
	}

	private void close(Connection connection, InputStream stdOut, InputStream stdErr) throws LinuxShellException {
		try {
			if (connection != null) {
				connection.close();
			}
			if (stdOut != null) {
				stdOut.close();
			}
			if (stdErr != null) {
				stdErr.close();
			}
		} catch (IOException e) {
			throw new LinuxShellException(e);
		}
	}

	private String processStream(InputStream inputStream) throws IOException {
		byte[] temp = new byte[1024 * 5];
		StringBuilder stringBuilder = new StringBuilder();
		while (inputStream.read(temp) != -1) {
			stringBuilder.append(new String(temp, CHARSET));
		}
		return stringBuilder.toString().trim();
	}

	private String array2String(String[] array) {
		StringBuilder stringBuilder = new StringBuilder("[");
		for (int i = 0, l = array.length; i < l; ++i) {
			if (i == l - 1) {
				stringBuilder.append(array[i]);
			} else {
				stringBuilder.append(array[i]).append(",");
			}
		}
		stringBuilder.append("]");
		return stringBuilder.toString();
	}

}